package com.lz.service;

import com.lz.UserDTO;
import com.lz.entity.QStudent;
import com.lz.entity.QUser;
import com.lz.entity.User;
import com.lz.repository.UserRepository;
import com.querydsl.core.QueryResults;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 执行先用maven Compile一下
 *
 * a、第一个方法是根据用户名与密码进行查询，QUser中有一个静态user属性，直接生成QUser的实例，QueryDSL都是围绕着这个QXxx来进行操作的。代码很直观，selectFrom是select方法与from方法的合并，这里为了方便就不分开写了，where中可以收可变参数Predicate，由于Predicate是一个接口，由user.username.eq或者user.uIndex.gt等等方法返回的都是BooleanExpression或者XXXExpression，这些XXXExpression都是Predicate的实现，故直接传入，让QueryDSL在内部做处理，其实Predicate也是实现了Expression接口，大家如果有兴趣可以自行跟踪源码研究。
 *
 * b、第二个方法也相当的直观，跟sql的字面意思几乎一模一样。
 *
 * c、第三个方法是第二个方法的排序写法，主要用到了offerset、limit方法，根据传入的pageable参数进行分页，最后返回的结果是一个QuerResults类型的返回值，该返回值对象简单的封装了一些分页的参数与返回的实体集，然调用者自己根据需求去取出使用。
 *
 * d、第四个方法展示了日期查询，也相当的直观，大家尝试了就知道了，主要使用到了between方法。
 *
 * e、第五个方法是比较重要的方法，这个方法展示了如何进行部分字段的映射查询，这个方法的目的是只查询uerrname、userId、nickname、birthday四个字段，然后封装到UserDTO中，最后返回。其中，由于select与from拆分了以后返回的泛型类型就是Tuple类型（Tuple是一个接口，它可以根据tuple.get(QUser.username)的方式获取User.username的真实值，暂时将他理解为一个类型安全的Map就行），根据pageable参数做了分页处理，fetch之后就返回了一个List<Tuple>对象。从fetch()方法之后，使用到了Stream，紧接着使用Java8的高阶函数map，这个map函数的作用是将List<Tuple>中的Tuple元素依次转换成List<UserDTO>中的UserDTO元素，在这个过程中我们还可以做bean的属性类型转换，将User的Date、Integer类型都转换成String类型。最后，通过collect结束stream，返回一个我们需要的List<UserDTO>。
 *
 * f、第六个方法与第五个方法的效果相同，使用QueryDSL的Projections实现。但是有一点，当User实体的属性类型与UserDTO中的属性类型不相同时，不方便转换。除了属性类型相同时转换方便以外，还是建议使用map函数进行操作。
 *
 * g、第七、第八个方法展示了QueryDSL与SpringDataJPA的联合使用，由于我们的UserRepository继承了QueryDslPredicateExecutor，所以获得了联合使用的支持。来看一看QueryDslPredicateExcutor接口的源码：
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    JPAQueryFactory jpaQueryFactory;

    //////////////////////////以下展示使用原生的dsl/////////////////////

    /**
     * 根据用户名和密码查找（假定只能找出一条）
     *
     * @param username
     * @param password
     * @return
     */
    public User findByUsernameAndPassword(String username, String password) {
        QUser user = QUser.user;
        return jpaQueryFactory
                .selectFrom(user)
                .where(
                        user.username.eq(username),
                        user.password.eq(password)
                )
                .fetchOne();
    }

    /**
     * 查询所有的实体,根据uIndex字段排序
     *
     * @return
     */
    public List<User> findAll() {
        QUser user = QUser.user;
        return jpaQueryFactory
                .selectFrom(user)
                .orderBy(
                        user.uIndex.asc()
                )
                .fetch();
    }

    /**
     * 分页查询所有的实体,根据uIndex字段排序
     *
     * @return
     */
    public QueryResults<User> findAllPage(Pageable pageable) {
        QUser user = QUser.user;
        return jpaQueryFactory
                .selectFrom(user)
                .orderBy(
                        user.uIndex.asc()
                )
                .offset(pageable.getOffset())   //起始页
                .limit(pageable.getPageSize())  //每页大小
                .fetchResults();    //获取结果，该结果封装了实体集合、分页的信息，需要这些信息直接从该对象里面拿取即可
    }

    /**
     * 根据起始日期与终止日期查询
     *
     * @param start
     * @param end
     * @return
     */
    public List<User> findByBirthdayBetween(Date start, Date end) {
        QUser user = QUser.user;
        return jpaQueryFactory
                .selectFrom(user)
                .where(
                        user.birthday.between(start, end)
                )
                .fetch();
    }

    /**
     * 部分字段映射查询
     * 投影为UserRes,lambda方式(灵活，类型可以在lambda中修改)
     *
     * @return
     */
    public List<UserDTO> findAllUserDto(Pageable pageable) {
        QUser user = QUser.user;
        List<UserDTO> dtoList = jpaQueryFactory
                .select(
                        user.username,
                        user.userId,
                        user.nickName,
                        user.birthday
                )
                .from(user)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch()
                .stream()
                .map(tuple -> UserDTO.builder()
                        .username(tuple.get(user.username))
                        .nickname(tuple.get(user.nickName))
                        .userId(tuple.get(user.userId).toString())
                        .birthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tuple.get(user.birthday)))
                        .build()
                )
                .collect(Collectors.toList());

        return dtoList;
    }

    /**
     * 部分字段映射查询
     * 投影为UserRes，自带的Projections方式,不够灵活，不能转换类型，但是可以使用as转换名字
     *
     * @return
     */
    /*public List<UserDTO> findAllDto2() {
        QUser user = QUser.user;
        List<UserDTO> dtoList = jpaQueryFactory
                .select(
                        Projections.bean(
                                UserDTO.class,
                                user.username,
                                user.userId,
                                user.nickName,
                                user.birthday
                        )
                )
                .from(user)
                .fetch();
        return dtoList;
    }*/

    //////////////////////////以下展示使用与SpringDataJPA整合的dsl/////////////////////

    /**
     * 根据昵称与用户名查询，并且根据uIndex排序
     *
     * @param nickName
     * @return
     */
    public List<User> findByNicknameAndUsername(String nickName, String username) {
        QUser user = QUser.user;
        List<User> users = (List<User>) userRepository.findAll(
                user.nickName.eq(nickName)
                        .and(user.username.eq(username)),
                user.uIndex.asc()   //排序参数
        );
        return users;
    }

    /**
     * 统计名字像likeName的记录数量
     *
     * @return
     */
    public long countByNickNameLike(String likeName) {
        QUser user = QUser.user;
        return userRepository.count(
                user.nickName.like("%" + likeName + "%")
        );
    }

    //////////////////////////展示dsl动态查询////////////////////////////////

    /**
     * 所有条件动态分页查询
     *
     * @param username
     * @param password
     * @param nickName
     * @param birthday
     * @param uIndex
     * @return
     */
    public Page<User> findByUserProperties(Pageable pageable, String username, String password, String nickName, Date birthday, BigDecimal uIndex) {
        QUser user = QUser.user;
        //初始化组装条件(类似where 1=1)
        Predicate predicate = user.isNotNull().or(user.isNull());

        //执行动态条件拼装
        predicate = username == null ? predicate : ExpressionUtils.and(predicate, user.username.eq(username));
        predicate = password == null ? predicate : ExpressionUtils.and(predicate, user.password.eq(password));
        predicate = nickName == null ? predicate : ExpressionUtils.and(predicate, user.nickName.eq(username));
        predicate = birthday == null ? predicate : ExpressionUtils.and(predicate, user.birthday.eq(birthday));
        predicate = uIndex == null ? predicate : ExpressionUtils.and(predicate, user.uIndex.eq(uIndex));

        Page<User> page = userRepository.findAll(predicate, pageable);
        return page;
    }

    /**
     * 动态条件排序、分组查询
     *
     * @param username
     * @param password
     * @param nickName
     * @param birthday
     * @param uIndex
     * @return
     */
    public List<User> findByUserPropertiesGroupByUIndex(String username, String password, String nickName, Date birthday, BigDecimal uIndex) {

        QUser user = QUser.user;
        //初始化组装条件(类似where 1=1)
        Predicate predicate = user.isNotNull().or(user.isNull());
        //执行动态条件拼装
        predicate = username == null ? predicate : ExpressionUtils.and(predicate, user.username.eq(username));
        predicate = password == null ? predicate : ExpressionUtils.and(predicate, user.password.eq(password));
        predicate = nickName == null ? predicate : ExpressionUtils.and(predicate, user.nickName.eq(username));
        predicate = birthday == null ? predicate : ExpressionUtils.and(predicate, user.birthday.eq(birthday));
        predicate = uIndex == null ? predicate : ExpressionUtils.and(predicate, user.uIndex.eq(uIndex));
        //执行拼装好的条件并根据userId排序，根据uIndex分组
        List<User> list = jpaQueryFactory
                .selectFrom(user)
                .where(predicate)               //执行条件
                .orderBy(user.userId.asc())     //执行排序
                .groupBy(user.uIndex)           //执行分组
                .having(user.uIndex.longValue().max().gt(7))//uIndex最大值小于7
                .fetch();

        //封装成Page返回
        return list;
    }

   public List<Tuple> findUserAndtudent(){
        QUser qUser =QUser.user;
        QStudent qStudent = QStudent.student;
       List<Tuple> fetch = jpaQueryFactory.select(qUser.username,qStudent.high).from(qUser, qStudent).where(qUser.sId.eq(qStudent.id)).fetch();
       return fetch;
   }
}